/*
Copyright (C) Ariff Ehsan, Abdul Hadi Fikri

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/*  Author : Ariff Ehsan
 *           Abdul Hadi Fikri
 *  Date   : June 22, 2009
 */


#include <system.h>
#include <serial0/serial0.h>


/**
*Interface serial0{
*	Provides{
*		command void SERIAL0_init(void);
*		command void SERIAL0_reg(void (*bytedone)(void), 
*					             void (*streamdone)(error_t), 
*				 	             void (*receive)(uint8_t));
*		command error_t SERIAL0_sendByte(uint8_t);
*		command error_t SERIAL0_sendStream(uint8_t *, uint8_t);
*	}
*	Uses{
*		signal void (*bytedone)(void);
*		signal void(*streamdone)(error_t);
*		signal void (*receive)(uint8_t));
*	}
*}
*
*Component{
*	call{
*		USART0.DRIVER_USART0_init();
*		USART0.DRIVER_USART0_REG(SERIAL0_USART0_sendDone, SERIAL0_USART0_receive);
*		USART0.DRIVER_USART0_SEND(data);
		
		TASK.TASK_reg(&serial0_send_task,SERIAL0_SEND_TASK_run(void));
		TASK.TASK_reg(&serial0_recv_task,SERIAL0_RECV_TASK_run(void));
		TASK.TASK_post(&serial0_send_task);
		TASK.TASK_post(&serial0_recv_task);
*	}
*	event{
*		SERIAL0_USART0_sendDone(void);
*       SERIAL0_USART0_receive(uint8_t);
*
*    	SERIAL0_SEND_TASK_run(void);
*		SERIAL0_RECV_TASK_run(void);
*	}
*}
*
**
*Data Field:
*/	enum {
		SERIAL0_SEND_TYPE_BYTE = 0,
		SERIAL0_SEND_TYPE_STREAM,
	
		SERIAL0_SEND_RETRY_MAX = 3,

		SERIAL0_RECV_BUFF_SIZE = 8,
	};

	BOOL serial0_busy;

	uint8_t *serial0_pdata;
	uint8_t serial0_data_len;
	uint8_t serial0_data_count;
	uint8_t serial0_send_retry;

	uint8_t serial0_send_type;

	void (*serial0_send_byte_done)(void);
	void (*serial0_send_stream_done)(error_t);
	void (*serial0_receive)(uint8_t);

	task_t serial0_send_task;

	uint8_t serial0_recv_data[SERIAL0_RECV_BUFF_SIZE];
	uint8_t serial0_recv_in;
	uint8_t serial0_recv_out;
	BOOL serial0_recv_busy, serial0_recv_full;

	task_t serial0_recv_task;
/**
*Function Field:
*/	void SERIAL0_defaultSendByteDone(void);
	void SERIAL0_defaultSendStreamDone(error_t);
	void SERIAL0_defaultReceive(uint8_t);

	void SERIAL0_SEND_TASK_run(void);
	void SERIAL0_RECV_TASK_run(void);

	void SERIAL0_USART0_sendDone(void);
	void SERIAL0_USART0_receive(uint8_t);

/********************************************************************************************/
/*                                  Clients Function Call                                   */
/********************************************************************************************/
void SERIAL0_reg(void (*bytedone)(void), void (*streamdone)(error_t), void (*receive)(uint8_t)) {
  if (bytedone != NULL) {
	  serial0_send_byte_done = bytedone;
	}

	if (streamdone != NULL) {
	  serial0_send_stream_done = streamdone;
	}
	
	if (receive != NULL) {
	  serial0_receive = receive;
	}
}

error_t SERIAL0_sendByte(uint8_t data) {
  if (serial0_busy) {
	  return EBUSY;
	}

  serial0_send_type = SERIAL0_SEND_TYPE_BYTE;
	serial0_busy = TRUE;

	if (DRIVER_USART0_SEND(data) != SUCCESS) {
	  serial0_busy = FALSE;
		return FAIL;
	}
	
	return SUCCESS;
}

error_t SERIAL0_sendStream(uint8_t *data, uint8_t len) {
  if (serial0_busy) {
	  return EBUSY;
	}

	serial0_busy = TRUE;
	serial0_send_type = SERIAL0_SEND_TYPE_STREAM;

  serial0_pdata = data;
	serial0_data_len = len;
	serial0_data_count = 0;
	serial0_send_retry = 0;

	TASK_post(&serial0_send_task);

  return SUCCESS;
}
/********************************************************************************************/
/*                                 Internal Function Call                                   */
/********************************************************************************************/
/**
 *  DEFAULT FUNCTIONS
 */

void SERIAL0_defaultSendByteDone() {
}

void SERIAL0_defaultSendStreamDone(error_t error) {
}

void SERIAL0_defaultReceive(uint8_t data) {
}

/********************************************************************************************/
/*                              HW/SW Interrupt Function Call                               */
/********************************************************************************************/
void SERIAL0_init(void) {
	DRIVER_USART0_INIT();
	
    serial0_busy = FALSE;

    serial0_send_byte_done = SERIAL0_defaultSendByteDone;
	serial0_send_stream_done = SERIAL0_defaultSendStreamDone;
	serial0_receive = SERIAL0_defaultReceive;

	serial0_recv_in = serial0_recv_out = 0;
	serial0_recv_busy = serial0_recv_full = FALSE;

	TASK_reg(&serial0_send_task, SERIAL0_SEND_TASK_run);
	TASK_reg(&serial0_recv_task, SERIAL0_RECV_TASK_run);

	DRIVER_USART0_REG(SERIAL0_USART0_sendDone, SERIAL0_USART0_receive);
}

void SERIAL0_USART0_sendDone(void) {
  
	if (serial0_send_type == SERIAL0_SEND_TYPE_BYTE) {
	  serial0_busy = FALSE;
		(*serial0_send_byte_done)();
	}
	else if (serial0_send_type == SERIAL0_SEND_TYPE_STREAM) {
	  serial0_send_retry = 0;
    serial0_data_count++;

		TASK_post(&serial0_send_task);
	}

}

void SERIAL0_USART0_receive(uint8_t data) {
  
  if (serial0_recv_full) {
	  return;
	}
	
	

  uint8_t sreg = SREG;
  cli();
  
  serial0_recv_data[serial0_recv_in] = data;
	serial0_recv_in = (serial0_recv_in + 1) % SERIAL0_RECV_BUFF_SIZE;

	if (serial0_recv_in == serial0_recv_out) serial0_recv_full = TRUE;

	if (!serial0_recv_busy) {
	  serial0_recv_busy = TRUE;
	  
	  SREG = sreg;
	  
	  TASK_post(&serial0_recv_task);
	}
	else {
		SREG = sreg;
	}

}

void SERIAL0_SEND_TASK_run(void) {
  
  if (serial0_data_count >= serial0_data_len) {
	  serial0_busy = FALSE;
		(*serial0_send_stream_done)(SUCCESS);
		
		return;
	}

	if (DRIVER_USART0_SEND(serial0_pdata[serial0_data_count]) != SUCCESS) {
	  if (++serial0_send_retry > SERIAL0_SEND_RETRY_MAX) {
		  serial0_busy = FALSE;
			(*serial0_send_stream_done)(ERETRY);

			return;
		}

		TASK_post(&serial0_send_task);
	}

}

void SERIAL0_RECV_TASK_run() {
  
  if (serial0_recv_in == serial0_recv_out && !serial0_recv_full) {
	  serial0_recv_busy = FALSE;
		return;
	}

  uint8_t data = serial0_recv_data[serial0_recv_out];

  (*serial0_receive)(data);

  uint8_t sreg = SREG;

	serial0_recv_out = (serial0_recv_out + 1) % SERIAL0_RECV_BUFF_SIZE;
	serial0_recv_full = FALSE;
  
	SREG = sreg;

  TASK_post(&serial0_recv_task);

}
